require "ipc/json"
require "json"

module AuthD
	class Client < IPC
		property server_fd : Int32 = -1

		def initialize
			super
			fd = self.connect "auth"
			if fd.nil?
				raise "couldn't connect to 'auth' IPC service"
			end
			@server_fd = fd
		end

		def read
			slice = self.read @server_fd
			m = IPCMessage::TypedMessage.deserialize slice
			m.not_nil!
		end

		# `parse_message` only raises exception in case the response could not be anticipated.
		# Any possibly expected error for a request should be in `expected_messages`.
		def parse_message(expected_messages, message)
			em = Array(IPC::JSON.class).new
			expected_messages.each do |e|
				em << e
			end
			# response = AuthD.responses.parse_ipc_json read
			em.parse_ipc_json message
		end

		def login?(login : String, password : String)
			send_now Request::Login.new login, password
			parse_message [Response::Login, Response::ErrorInvalidCredentials], read
		end

		def get_user?(uid_or_login : UserID)
			send_now Request::GetUser.new uid_or_login
			parse_message [
					Response::User,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound
				], read
		end

		def send_now(msg : IPC::JSON)
			m = IPCMessage::TypedMessage.new msg.type.to_u8, msg.to_json
			write @server_fd, m
		end

		def send_now(type : Request::Type, payload)
			m = IPCMessage::TypedMessage.new type.value.to_u8, payload
			write @server_fd, m
		end

		# FIXME: Extra options may be useful to implement here.
		def add_user(login : String, password : String,
			admin : Bool,
			email : String?,
			profile : Hash(String, ::JSON::Any)?)

			send_now Request::AddUser.new login, password, admin, email, profile
			parse_message [
					Response::UserAdded,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorAlreadyUsedLogin,
					Response::ErrorMailRequired
				], read
		end

		# Migration of a user from old code base (dnsmanager v1).
		def migrate_user(login : String, password_hash_brkn : String)
			send_now Request::MigrateUser.new login, password_hash_brkn
			parse_message [
					Response::UserAdded,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorAlreadyUsedLogin,
					Response::ErrorMailRequired
				], read
		end

		def bootstrap(login : String,
		              password : String,
		              email : String,
		              profile : Hash(String, ::JSON::Any)? = nil)

			send_now Request::BootstrapFirstAdmin.new login, password, email, profile
			parse_message [Response::UserAdded,Response::ErrorAlreadyUsersInDB], read
		end

		def decode_token(token)
			send_now Request::DecodeToken.new token
			parse_message [
					Response::User,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound
				], read
		end

		def exit
			send_now Request::Exit.new
		end

		def validate_user(login : String, activation_key : String)
			send_now Request::ValidateUser.new login, activation_key
			parse_message [
					Response::UserValidated,
					Response::ErrorUserNotFound,
					Response::ErrorUserAlreadyValidated,
					Response::ErrorInvalidActivationKey
				], read
		end

		def ask_password_recovery(uid_or_login : UserID)
			send_now Request::AskPasswordRecovery.new uid_or_login
			parse_message [Response::PasswordRecoverySent, Response::ErrorUserNotFound], read
		end

		def change_password(uid_or_login : UserID, new_pass : String, renew_key : String)
			send_now Request::PasswordRecovery.new uid_or_login, renew_key, new_pass
			parse_message [
					Response::PasswordRecovered,
					Response::ErrorUserNotFound,
					Response::ErrorInvalidRenewKey
				], read
		end

		def register(login : String,
			password : String,
			email : String?,
			profile : Hash(String, ::JSON::Any)?)

			send_now Request::Register.new login, password, email, profile
			parse_message [
					Response::UserAdded,
					Response::ErrorRegistrationsClosed,
					Response::ErrorAlreadyUsedLogin,
					Response::ErrorInvalidLoginFormat,
					Response::ErrorMailRequired,
					Response::ErrorInvalidEmailFormat,
					Response::ErrorPasswordTooShort,
					Response::ErrorPasswordTooLong,
					Response::ErrorCannotContactUser
				], read
		end

		def mod_user(uid_or_login : UserID, password : String? = nil, email : String? = nil, admin : Bool? = nil)
			request = Request::ModUser.new uid_or_login, admin, password, email

			send_now request
			parse_message [
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound,
					Response::UserEdited
				], read
		end

		def check_permission(user : UserID, service_name : String, resource_name : String)
			request = Request::CheckPermission.new user, service_name, resource_name
			send_now request
			parse_message [
					Response::PermissionCheck,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound
				], read
		end

		def set_permission(user : UserID, service : String, resource : String, permission : User::PermissionLevel)
			request = Request::SetPermission.new user, service, resource, permission
			send_now request
			parse_message [
					Response::PermissionSet,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound
				], read
		end

		def search_user(user_login : String)
			send_now Request::SearchUser.new user_login
			parse_message [Response::MatchingUsers, Response::ErrorMustBeAuthenticated], read
		end

		def edit_profile_content(user : UserID, new_values)
			send_now Request::EditProfileEntries.new user, new_values
			parse_message [
					Response::User,
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound,
					Response::ErrorReadOnlyProfileKeys
				], read
		end

		def delete(user : UserID)
			send_now Request::Delete.new user
			parse_message [
					Response::ErrorMustBeAuthenticated,
					Response::ErrorUserNotFound,
					Response::UserDeleted
				], read
		end
	end
end
